package mviewer.renderer.software;

import java.awt.Graphics2D;
import java.awt.Polygon;
import java.util.Iterator;
import java.util.Map;
import java.util.Stack;
import mviewer.renderer.software.core.Matrix3d;
import mviewer.renderer.software.interfaces.IBuffer;
import mviewer.renderer.software.interfaces.ILighting;
import mviewer.renderer.software.interfaces.IPerspective;
import mviewer.renderer.software.interfaces.IShader;
import mviewer.renderer.software.shader.FlatShader;
import mviewer.renderer.software.shader.GouraudShader;
import mviewer.types.Camera;
import mviewer.types.IDrawer;
import mviewer.types.IDrawerContext;
import mviewer.types.IModel3d;
import mviewer.types.IPolygon;
import mviewer.types.IVector;
import mviewer.types.IWritableVector;
import mviewer.types.Material;
import mviewer.types.SegregatedDataIterator;
import mviewer.types.Transformation;
import mviewer.types.VectorUtil;

public class SimpleSoftwareDrawer implements IDrawerContext, IDrawer {

	private IShader shader = new FlatShader();//new GouraudShader();
	private IPerspective perspective;
	private ILighting lighting;
	private IBuffer buffer;
	private Camera camera;
	private Map<String, IModel3d> modelInstances;
	private Stack<Matrix3d> matrices;
	protected Graphics2D graphics;

	public SimpleSoftwareDrawer(ILighting lighting, IPerspective perspective,
			Camera camera, IBuffer buffer, Graphics2D graphics,
			Stack<Matrix3d> matrices, Map<String, IModel3d> modelInstances) {
		this.matrices = matrices;
		this.perspective = perspective;
		this.lighting = lighting;
		this.camera = camera;
		this.buffer = buffer;
		this.graphics = graphics;
		this.modelInstances = modelInstances;
	}

	@Override
	public IDrawer getDrawer(int feature) {
		return this;
	}

	@Override
	public void pushTransformation(Transformation transformation) {
		Matrix3d matrix = new Matrix3d();

		matrix.unit();
		matrix.mult(matrices.lastElement());
		matrices.push(matrix);
		applyTransformation(matrix, transformation);
	}

	@Override
	public void draw(SegregatedDataIterator<IPolygon> polygons, Material material) {
		int width = buffer.getWidth(), height = buffer.getHeight(),
			xOffset = width/2,
			yOffset = height - height/2;
		Matrix3d matrix = matrices.lastElement();
		IModel3d source = getSource(polygons.getName()),
				 target = getInstance(polygons.getName());

		transform(matrix, source, target);

		while (polygons.hasNext())
			drawPolygon(polygons.next(), xOffset, yOffset);
	}

	protected void drawPolygon(IPolygon polygon, int xOffset, int yOffset) {
			float polygonDirection = VectorUtil.scalar(polygon.getNormal(), camera.getNormal());

			if (polygonDirection >= 0) { // backface removal
				// render polygon image coordinates
				Polygon polygon2d = perspective.render2dPolygon(polygon,
													camera,	xOffset, yOffset);
				// let shader draw polygon
				shader.drawPolygon(graphics, polygon, polygon2d, lighting, buffer);
			}
	}

	@Override
	public void popTransformation() {
		matrices.pop();
	}

	private IModel3d getInstance(String name) {
		IModel3d m = modelInstances.get(name);

		if (m == null)
			throw new IllegalArgumentException(String.format("Unkown model instance: %s", name));

		return m;
	}

	private IModel3d getSource(String name) {
		return getInstance(name.substring(0, name.length() - IModel3d.COPY_SUFFIX.length()));
	}

	private void transform(Matrix3d matrix, IModel3d source, IModel3d target) {
		IWritableVector srcVector, tgtVector;
		Iterator<IWritableVector> srcVertexIter = source.transformableVertexIterator();
		Iterator<IWritableVector> tgtVertexIter = target.transformableVertexIterator();
		Iterator<IWritableVector> srcNormalIter = source.transformableNormalIterator();
		Iterator<IWritableVector> tgtNormalIter = target.transformableNormalIterator();

		// transform vertices
		while (srcVertexIter.hasNext()) {
			srcVector = srcVertexIter.next();
			tgtVector = tgtVertexIter.next();
			matrix.transform(srcVector, tgtVector);
		}

		// transform normals
		while (srcNormalIter.hasNext()) {
			srcVector = srcNormalIter.next();
			tgtVector = tgtNormalIter.next();
			matrix.transform(srcVector, tgtVector);
			VectorUtil.normalize(tgtVector);
		}
	}

	private void applyTransformation(Matrix3d m, Transformation t) {
		// apply rotation to matrix;
		IVector v = t.getRotation();
		m.xrot(v.getX());
		m.yrot(v.getY());
		m.zrot(v.getZ());
		// apply scaling to matrix;
		v = t.getScaling();
		m.scale(v.getX(), v.getY(), v.getZ());
		// apply translation to matrix;
		v = t.getTranslation();
		m.translate(v.getX(), v.getY(), v.getZ());
	}

}
